package operations;


import java.io.File;
import java.io.IOException;
import java.util.Calendar;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.logging.Level;

import executor.ReflectionExecutor;
import ga.GAInfo;
import ga.Individual;
import util.GAConfig;
import util.CanBeUsedAs;
import util.GAOperationsConfig;
import util.Log;
import util.Randomness;
import localSearch.LocalSearch;
import model.StatementKind;
import model.CUT;
import model.Filter;
import model.TestClusterBuilder;
import model.ConstructorDeclared;
import model.MethodDeclared;
public abstract class Operation   {
	// generation probabilities
    public static float PDiv = 0.25f;
    public static float GEN_NEW = 0.35f;
    public static float USE_CONSTANTS =0.15f;
    public static float SET_TO_NULL = 0.05f;
	public static float Select_Class_Cluster = 0.25f;//1.0f;

    public static List<Class<?>> replacementObjectParam= new LinkedList<Class<?>>();

	@Override
	public abstract Operation clone();
	public static  List<Operation> generateInitialConstructors(TestClusterBuilder TestCluster){
		Map<String, CUT>  cluster= TestCluster.getCluster();
		Map<String, String[]> refFactory=TestCluster.getRefFactory();
		List<Operation> initialConstructors= new LinkedList<Operation> ();
		//Create Arrays first
		for(String cName: cluster.keySet()){
			CUT cut=cluster.get(cName);
			 Class<?> t = cut.getClazz();
			 if(t.isArray()){
					String[] refrences = refFactory.get(cName);
					for (int i = 0; i < refrences.length; i++) {
							Operation creatArry=Operation.createArray(t,TestCluster);
							if(creatArry!=null)
								initialConstructors.add(creatArry);
					}
			 }
		}
		for(String cName: cluster.keySet()){
			CUT cut=cluster.get(cName);
	//		System.out.println("Operation.generateInitialConstructors: "+ cName);
			String[] refrences = refFactory.get(cName);
			for (int i = 0; i < refrences.length; i++) {
				Operation op =CreateObject.generate(cut,TestCluster);
				if (op==null)
					op = AssignConstant.generate(cut, TestCluster);
				if(op!=null){
					if(op instanceof CreateObject )
						((CreateObject)op).setRefrence(refrences[i])	;
					else
						((AssignConstant)op).setRefrence(refrences[i])	;
						
					initialConstructors.add(op);
				}
			}

		}
		return initialConstructors;
	}
	public static List<Operation> randomlyGenerate(TestClusterBuilder TestCluster, int testLength){
		List<Operation> ops = new LinkedList<Operation>();
		while (ops.size()<testLength) {
			List<Operation> op = new LinkedList<Operation>();
			CUT cut=null;
			try {
				 cut=TestCluster.getCut();
				if(ValuesGenerator.getBoolean(Select_Class_Cluster))  cut=TestCluster.getClassCluster();
//				while(ValuesGenerator.getBoolean(1-PDiv)){
					if (ValuesGenerator.getBoolean(GEN_NEW)) {
						if(cut.getClazz().isArray())
							op.add(Operation.createArray(cut.getClazz(),TestCluster));
						else
							if (ValuesGenerator.getBoolean(USE_CONSTANTS))
								op.add(AssignConstant.generate(cut, TestCluster));
							else 							
								op.add( CreateObject.generate(cut, TestCluster));

					} 
					else{
						
						if(cut.getMethods().length!=0)
							op.addAll( InvokeMethod.generate(cut, TestCluster));
					}
					if (op.size()>0&&!op.contains(null))
						ops.addAll(op);
//				}//while(ValuesGenerator.getBoolean(1-PDiv))
			} catch (Throwable e) {
				Log.log(Level.WARNING, "Operation.randomlyGenerat: "
						+ e.getMessage());
				System.err.println("Operation.randomlyGenerat: "+ cut.getClazz().getName() + " error "+e.getMessage());
			}
		}
		return ops;
	}
	public static List<Operation> recommendedGenerate(TestClusterBuilder TestCluster, int testLength,Map<Class<?>,  List<StatementKind>> recStatements){
		List<Operation> ops = new LinkedList<Operation>();
		
		while (ops.size()<testLength) {
			try {
				List<Operation> op = new LinkedList<Operation>();

				List<Class<?>> recClassToTest= new ArrayList<Class<?>>(recStatements.keySet());
				Class<?> clz=recClassToTest.get(Randomness.nextRandomInt(recClassToTest.size()));
				List<StatementKind> statements=recStatements.get(clz);
				StatementKind sk=null;
				if(statements.size()!=0)
					sk=statements.get(Randomness.nextRandomInt(statements.size()));
				
				if(sk!=null){
					if(sk instanceof ConstructorDeclared){
						clz=((ConstructorDeclared) sk).getConstructor().getDeclaringClass();
						if (ValuesGenerator.getBoolean(GEN_NEW)) {
						if (ValuesGenerator.getBoolean(USE_CONSTANTS))
							op.add(AssignConstant.recommendedgenerate(clz,sk, TestCluster));
						else 
							op.add( CreateObject.recommendedgenerate(clz,sk, TestCluster));
						}
					}
					else{
						clz=((MethodDeclared) sk).getMethod().getDeclaringClass();
						op.addAll( InvokeMethod.recommendedgenerate(clz,sk, TestCluster));
					}
				}
				else{
					CUT cut=TestCluster.getCluster().get(clz.getName());
					op.add(AssignConstant.generate(cut, TestCluster));
				}
				if (op.size()>0&&!op.contains(null))
					ops.addAll(op);
			} catch (Throwable e) {
				Log.log(Level.WARNING, "Operation.recommendedGenerate: "
						+ e.getMessage());
				System.err.println("Operation.recommendedGenerate: "
						+ e.getMessage());
			}
		}
		return ops;
	}

	public static Object generateReference(Class<?> clz1,TestClusterBuilder TestCluster) {
		Map<String, CUT>  cluster = TestCluster.getCluster();
		Map<String, String[]> refFactory = TestCluster.getRefFactory();
		List <Class<?>> equivalentClass=new LinkedList<Class<?>>();
		Class<?> clz2=null;;
		List<String> keys = new ArrayList<String>(cluster.keySet());
		for( ;equivalentClass.size()!=cluster.size();){
			int cutRand = Randomness.random.nextInt(cluster.size());

			String cName = (String) keys.get(cutRand);

			 clz2=((CUT)cluster.get(cName)).getClazz();
			 if((CanBeUsedAs.canBeUsedAs(clz2, clz1)))
				 	break;
			if(!equivalentClass.contains(clz2))
				equivalentClass.add(clz2);
			}//while(!CanBeUsedAs.canBeUsedAs(clz2, clz1));
		 if(equivalentClass.size()==cluster.size()){
	//		 System.err.println("Operation.generateReference() There is no type for "+clz1.getName());
//			 if(DebugConfig.TestClusterBuilder)
//				 Log.log("Operation.generateReference() There is no type for "+clz1.getName());
			
			 return null;
		 }
		 replacementObjectParam.add(clz2);
		if (Filter.isPrimitiveOrStringType(clz2))
			return (ValuesGenerator.generatePrimitiveValue(clz2));
		if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
			return null;
		return getReference(clz2.getName(), refFactory);
	}
	
	public static List<Object> selectInputs(StatementKind statement,TestClusterBuilder TestCluster){
		replacementObjectParam= new LinkedList<Class<?>>();

		   List<Class<?>> inputClasses = statement.getInputTypes();
		   List <Object> values=new LinkedList<Object>();
		   for (int i = 0; i < inputClasses.size(); i++) {
			      Class<?> t = inputClasses.get(i);
					if (Filter.isPrimitiveOrStringType(t))
						values.add(ValuesGenerator.generatePrimitiveValue(t));
					else
						values.add( generateReference(t, TestCluster));

//						if(t.isArray())
//							values.add(createArray(t,TestCluster));
//					else
		   }
		return values;
		
	}
	
	public static Operation createArray(Class<?> cls,TestClusterBuilder TestCluster){
		Operation  s=null;
		   if (Filter.isPrimitiveOrStringType(cls.getComponentType())) 
			      s = randPrimitiveArray(cls, TestCluster);
		   else
			   	  s= randNonPrimitiveArray(cls.getComponentType(),TestCluster);
		   return  s;
	}
	  private static Operation randNonPrimitiveArray(Class<?> componentType,TestClusterBuilder TestCluster) {
		  Object ref=generateReference(componentType, TestCluster);
		  if(ref==null) return null;
		  List<Object> inputs = new ArrayList<Object>();
		  CreateArray s = new CreateArray(ref.toString(),componentType, 0, inputs);
		  if  (ValuesGenerator.getBoolean(Operation.SET_TO_NULL))
			  return s;
		  else{
			    int length = Randomness.nextRandomInt(GAConfig.ArraySize);
			    for (int i = 0 ; i < length ; i++) {
			      inputs.add(generateReference(componentType, TestCluster));
			    }
			    s = new CreateArray(ref.toString(),componentType, length, inputs);
		  }
			  return s;
		  
	  }
	  private static Operation randPrimitiveArray(Class<?> cls,TestClusterBuilder TestCluster) {
		    assert cls.getComponentType().isPrimitive();
		    CreateArray s=null;
		    int length = Randomness.nextRandomInt(GAConfig.ArraySize);
		    List<Object> inputs = new ArrayList<Object>();
		    for (int i = 0 ; i < length ; i++) {
		      inputs.add(ValuesGenerator.generatePrimitiveValue(cls.getComponentType()));
		    }
		    Object ref=generateReference(cls, TestCluster);

		    if(ref!=null)
		    	s= new CreateArray(ref.toString(),cls.getComponentType(), length, inputs);
		    
		    return s;
		  }

	public static String getReference(String cName,Map<String, String[]>  refFactory) {
		String refs[] = refFactory.get(cName);
		
		if(refs == null) return null;

		return refs[Randomness.nextRandomInt(refs.length)];
	}

	public  static void main(String args[]) throws ClassNotFoundException,IOException {
		String logFilePath = "c:/MAMDA/MAMDA2014/temp/Operation.txt";
		Log.logConfig(logFilePath);
		new File(logFilePath).getAbsolutePath();

	       GAInfo     gaInfo     = new GAInfo();
			long startTime = Calendar.getInstance().getTimeInMillis();
			gaInfo.setTestTimeOut(60 * 500);
			 gaInfo.setTestTimeOut( 60 * 1000*1); ;

			gaInfo.setStartTime(startTime);
	       
	       Randomness.reset(20);
		
		 Set<Class<?>> allClasses = new LinkedHashSet<Class<?>>();
	      //get class from file
	      allClasses.addAll(TestClusterBuilder.readClassFromFile());

	      System.out.println("There are " + allClasses.size() + " classes as input");
	      
	      System.out.println(allClasses);
	//      TestClusterBuilder TestCluster = new TestClusterBuilder(allClasses);
	      List<TestClusterBuilder> TestClusterList= TestClusterBuilder.buildTestClusterList(allClasses);
	      for(TestClusterBuilder tCluster:TestClusterList){
		      Map<String,Class<?>> classesToTest = tCluster.getClassToTest();
		      CUT cut=tCluster.getCut();
		      System.out.println("Class " + classesToTest.keySet().toString() + " is being Tested");
		     // System.out.println(classesToTest.keySet().toString());
		      if(cut.getMethods().length==0){
			      System.out.println("Class " + classesToTest.keySet().toString() + " is being Tested");
			      System.out.println("\tThere are no methods to test!");
			      continue;
		      }

		      gaInfo.setTestCluster(tCluster);
		      
				LocalSearch branchInfo = GAOperationsConfig.getLocalSearch();
				branchInfo.setExtraInfo(gaInfo);
				gaInfo.setMapBranchCondition(branchInfo.readFileWGZ(gaInfo.getTestCluster()));

		      List<Operation> ops=new LinkedList<Operation>();
		      List<Operation> initialConstructors= Operation.generateInitialConstructors(tCluster);
		      ops.addAll(Operation.randomlyGenerate(tCluster,10));
		      System.out.println(ops.toString());
		      Individual individual=new Individual(initialConstructors,ops);
		      ReflectionExecutor.execute(gaInfo,individual);
	      }
	      System.exit(0);
	}


}